////////////////////////////////////////////////////////////////////////////////////////////////////
/// @file   CollabRCBotUnitTests\ConstantTreeNode.h
///
/// @brief  Defines the unit tests for CollabRC::Bot::AI::ConstantTreeNode
////////////////////////////////////////////////////////////////////////////////////////////////////

#include <cxxtest/TestSuite.h>
#include "../CollabRCBot/ConstantTreeNode.h"
#include "RandomMock.h"

#ifndef EPSILON
#define EPSILON 0.0000001
#define UNDEF_EPSILON
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
/// @class  ConstantTreeNodeTestSuite
///
/// @brief  Test suite for CollabRC::Bot::AI::ConstantTreeNode
///
/// @author Matthew P. Del Buono
/// @date   12/21/2009
////////////////////////////////////////////////////////////////////////////////////////////////////
class ConstantTreeNodeTestSuite : public CxxTest::TestSuite
{
public:
    /**
     * @brief Tests the CollabRC::Bot::AI::ConstantTreeNode::GetRandomBranch()
     * function.
     *
     * Ensures that the branch that is returned must be identical
     * to the current branch.
     */
    void TestRandomBranch() 
    {
        CollabRC::Bot::AI::ConstantTreeNode positive(1);
        CollabRC::Bot::AI::ConstantTreeNode negative(-1);

        // Test positive
        const CollabRC::Bot::AI::ComputationalTreeNode& node = positive.GetRandomBranch(0);
        TS_ASSERT_EQUALS(node.Compute(), positive.Compute());

        // Test negative
        const CollabRC::Bot::AI::ComputationalTreeNode& node2 = negative.GetRandomBranch(0);
        TS_ASSERT_EQUALS(node2.Compute(), negative.Compute());

        // Test beyond the branch
        const CollabRC::Bot::AI::ComputationalTreeNode& node3 = positive.GetRandomBranch(1);
        TS_ASSERT_EQUALS(node3.Compute(), positive.Compute());                 
    }

    /**
     * @brief Tests the CollabRC::Bot::AI::ConstantTreeNode::GetBranchDepth()
     * function.
     *
     * For a ConstantTreeNode, this function should always return 1.
     */
    void TestBranchDepth()
    {
        CollabRC::Bot::AI::ConstantTreeNode node(0);
        TS_ASSERT_EQUALS(node.GetBranchDepth(), 1);
    }

    /**
     * @brief Tests the cloning of a given ConstantTreeNode. 
     *
     * The result of a cloned ConstantTreeNode should be identical
     * to the original and should be located on the heap.
     */
    void TestClone()
    {
        CollabRC::Bot::AI::ConstantTreeNode positive(1);
        CollabRC::Bot::AI::ConstantTreeNode negative(-1);

        // Potential memory leaks here if the constructor(s) die, but
        // in a unit test it's not really worth the effort of correcting
        CollabRC::Bot::AI::ComputationalTreeNode *positiveClone = positive.Clone();
        CollabRC::Bot::AI::ComputationalTreeNode *negativeClone = negative.Clone();

        TS_ASSERT_EQUALS(positive.Compute(), positiveClone->Compute());
        TS_ASSERT_EQUALS(negative.Compute(), negativeClone->Compute());

        // Clean up - should fail if not on the heap
        delete positiveClone;
        delete negativeClone;
    }

    /**
     * @brief Tests mutation of a constant. 
     *
     * The mutated constant may not go beyond +/- 25% or +/- 0.25, 
     * whichever is larger. This is tested by overriding the rand()
     * function and replacing it with an implementation that operates
     * at each limit.
     */
    void TestMutate()
    {
        CollabRC::Bot::AI::ConstantTreeNode ctn10(10);         // Should be +/- 2.5
        CollabRC::Bot::AI::ConstantTreeNode ctn1(1);           // Should be +/- 0.25
        CollabRC::Bot::AI::ConstantTreeNode ctnTenth(0.1f);    // Should be +/- 0.25 (can change signs)
        CollabRC::Bot::AI::ConstantTreeNode ctn0(0);           // Should be +/- 0.25 (will change signs)
        CollabRC::Bot::AI::ConstantTreeNode ctnNegTenth(-0.1f);// Should be +/- 0.25 (can change signs)
        CollabRC::Bot::AI::ConstantTreeNode ctnNeg1(-1);       // Should be +/- 0.25
        CollabRC::Bot::AI::ConstantTreeNode ctnNeg10(-10);     // Should be +/- 2.5

        // We expect, for RAND_MAX, to get a change of +25% or +0.25
        MaxRandom mock1;
        
        CollabRC::Bot::AI::ComputationalTreeNode *test1 = ctn10.Mutate(0);
        CollabRC::Bot::AI::ComputationalTreeNode *test2 = ctn1.Mutate(0);
        CollabRC::Bot::AI::ComputationalTreeNode *test3 = ctnTenth.Mutate(0);
        CollabRC::Bot::AI::ComputationalTreeNode *test4 = ctn0.Mutate(0);
        CollabRC::Bot::AI::ComputationalTreeNode *test5 = ctnNegTenth.Mutate(0);
        CollabRC::Bot::AI::ComputationalTreeNode *test6 = ctnNeg1.Mutate(0);
        CollabRC::Bot::AI::ComputationalTreeNode *test7 = ctnNeg10.Mutate(0);

        TS_ASSERT_DELTA(ctn10.Compute() + (0.25 * ctn10.Compute()), test1->Compute(), EPSILON);
        TS_ASSERT_DELTA(ctn1.Compute() + (0.25 * ctn1.Compute()), test2->Compute(), EPSILON);
        TS_ASSERT_DELTA(ctnTenth.Compute() + 0.25, test3->Compute(), EPSILON);
        TS_ASSERT_DELTA(ctn0.Compute() + 0.25, test4->Compute(), EPSILON);
        TS_ASSERT_DELTA(ctnNegTenth.Compute() + 0.25, test5->Compute(), EPSILON);
        TS_ASSERT_DELTA(ctnNeg1.Compute() + 0.25, test6->Compute(), EPSILON);
        TS_ASSERT_DELTA(ctnNeg10.Compute() + (0.25 * ctnNeg10.Compute()), test7->Compute(), EPSILON);

        delete test1, test2, test3, test4, test5, test6, test7;
        
        // We expect, for 0, to get a change of -25% or -0.25
        MinRandom mock2;

        test1 = ctn10.Mutate(0);
        test2 = ctn1.Mutate(0);
        test3 = ctnTenth.Mutate(0);
        test4 = ctn0.Mutate(0);
        test5 = ctnNegTenth.Mutate(0);
        test6 = ctnNeg1.Mutate(0);
        test7 = ctnNeg10.Mutate(0);

        TS_ASSERT_DELTA(ctn10.Compute() - (0.25 * ctn10.Compute()), test1->Compute(), EPSILON);
        TS_ASSERT_DELTA(ctn1.Compute() - (0.25 * ctn1.Compute()), test2->Compute(), EPSILON);
        TS_ASSERT_DELTA(ctnTenth.Compute() - 0.25, test3->Compute(), EPSILON);
        TS_ASSERT_DELTA(ctn0.Compute() - 0.25, test4->Compute(), EPSILON);
        TS_ASSERT_DELTA(ctnNegTenth.Compute() - 0.25, test5->Compute(), EPSILON);
        TS_ASSERT_DELTA(ctnNeg1.Compute() - 0.25, test6->Compute(), EPSILON);
        TS_ASSERT_DELTA(ctnNeg10.Compute() - (0.25 * ctnNeg10.Compute()), test7->Compute(), EPSILON);

        delete test1, test2, test3, test4, test5, test6, test7;
    }
};

#ifdef UNDEF_EPSILON
#undef UNDEF_EPSILON
#undef EPSILON
#endif
